/*
MujMail - Simple mail client for J2ME
Copyright (C) 2003 Petr Spatka <petr.spatka@centrum.cz>
Copyright (C) 2005 Pavel Machek <pavel@ucw.cz>
Copyright (C) 2006 Nguyen Son Tung <n.sontung@gmail.com>
Copyright (C) 2008 David Hauzar <david.hauzar.mujmail@gmail.com>

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


package mujmail;

import mujmail.util.Functions;
import java.util.Enumeration;
import com.sun.lwuit.TextArea;
import com.sun.lwuit.Dialog;
import javax.microedition.lcdui.AlertType;
import lcduilwuit.*;
import LwuitUI.BoxRenderer;
import com.sun.lwuit.Image;
import com.sun.lwuit.events.*;
import com.sun.lwuit.list.*;
import java.util.Vector;
import com.sun.lwuit.layouts.*;
import mujmail.account.MailAccount;
import mujmail.ordering.ComparatorStrategy;
import mujmail.ordering.Criterion;
import mujmail.ordering.Ordering;
import mujmail.protocols.InProtocol;
import mujmail.threading.ThreadedEmails;

/**
 * Represents boxes: see Inbox, Outbox, ...
 * 
 * Each message is stored in the container and RMS database of one persistent box.
 * Moreover, it can be stored in the container of more Nonpersistent boxes.
 * See documentation of PersistentBox and UnpersistentBox for more details.
 * 
 * It displays the box. That is, it displays headers of mails in the box (paint()) and
 * it displays the progress bar (paintProgress(), report())
 */
public abstract class TheBox extends /*Canvas*/Form implements ActionListener,Displayable/*CommandListener*/ {
    
    /** Set to true if debug information should be displayed while reporting
     messages using methods report() */
    private static final boolean DEBUG = false;
    
    /** The name of this source file */
    private static final String SOURCE_FILE = "TheBox";
    
    //private boolean tickerEnabled =false;// true;

    /** The name of the box that is shown to user */
    private String name;
    
    protected final MujMail mujMail;
    /** Mails in the box. */
    protected IStorage storage;

    int deleted; //counter of mails that are going to be deleted
    /**
     * Index of currently selected message.
     * Even if threading is enabled this number is index to the storage vector
     * (the empty messages are skipped in this index).  
     */
    /**
     * This number indicates the number of empty message before {@link #cur}
     * index. It's used when showing index of the message in box.
     */
    int empties;
    Image imNormal, imDeleted, imRead, imAnswered, imSent, imFailedSent, imAttch, imFlagged, imRoot;
    public Command stop, exit, /*delete,*/ deleteNow, viewMessage,/* empty,*/ sort, seen,
            flagged, showHeader;
    boolean btnsHidden = false; //are some buttons hidden?	
    String activity = "";
    //represents sort mode of the box. 
    //the most right bit represents sort order, the other 3bits represents sort criteria
    //the meaning of criterion bits are defined in Functions.SRT_HDR_*
    //private byte sortMode;

    private Ordering ordering;
    private Criterion criterion;
    Vector vct;
    BoxRenderer bxr = new BoxRenderer();
    protected  MujMail getMujMail() {
        if (mujMail == null) {
            System.out.println("mujmail is null");
        }
        return MujMail.mujmail;
    }

    /**
     * Increments the number of deleted messages in this box.
     */
    public void incDeleted() {
        deleted++;
    }

    /**
     * @return the ordering
     */
    public Ordering getOrdering() {
        return ordering;
    }

    public void setOrdering(Ordering ordering) {
        this.ordering = ordering;
    }

    /**
     * @return the criterion
     */
    public Criterion getCriterion() {
        return criterion;
    }

    public void setCriterion(Criterion criterion) {
        this.criterion = criterion;
    }

    public IStorage getStorage() {
        return storage;
    }

    public void setStorage(ThreadedEmails storage) {
        //#ifdef MUJMAIL_DEBUG_CONSOLE
//#         DebugConsole.println("Setting storage " + storage);
//#         if (storage == null) {
//#             DebugConsole.println("Setting storage is null");
//#             return;
//#         }
        //#endif
        if ( DEBUG && storage != null ) {
              System.out.println("DEBUG InBox.setStorage(ThreadedEmails) - new storage size: " + (storage == null?"":Integer.toString( storage.getSize()) ) );
              System.out.println("DEBUG InBox.setStorage(ThreadedEmails) - new storage: " );
              //#ifdef MUJMAIL_DEVELOPMENT
//#               ((ThreadedEmails)storage).printToConsole();
              //#endif
          }
          
        this.storage = storage;
    }


    /**
     * Creates the box.
     * 
     * @param mMail 		the main object in the application
     * @param name 			the name of the box
     * @param searchable 	true if the box should be searchable
     */
    public TheBox(MujMail mMail, String name) {
        super(name);
        setLayout(new BorderLayout());
        setScrollable(false);
        this.name = name;
        mujMail = mMail;
        storage = new ThreadedEmails();
        imNormal = Functions.getLwuitIcon("m_normal.png");
        imDeleted = Functions.getLwuitIcon("m_deleted.png");
        imRead = Functions.getLwuitIcon("m_opened.png");
        imAnswered = Functions.getLwuitIcon("m_answered.png");
        imSent = Functions.getLwuitIcon("m_sent.png");
        imFailedSent = Functions.getLwuitIcon("m_failed_send.png");
        imAttch = Functions.getLwuitIcon("m_attachment.png");
        imFlagged = Functions.getLwuitIcon("m_flagged.png");
        imRoot = Functions.getLwuitIcon( "m_root.png" );

        exit = new Command(Lang.get(Lang.BTN_BACK), Command.BACK, 0);
        viewMessage = new Command(Lang.get(Lang.BTN_TB_VIEW_MESS), Command.OK, 1);
        stop = new Command(Lang.get(Lang.BTN_TB_STOP), Command.STOP, 2);
        deleteNow = new Command(Lang.get(Lang.BTN_TB_DEL_NOW), Command.ITEM, 5);
        sort = new Command(Lang.get(Lang.BTN_TB_SORT), Command.ITEM, 9);
        seen = new Command(Lang.get(Lang.BTN_TB_MARK_SEEN), Command.ITEM, 6);
        flagged = new Command(Lang.get(Lang.BTN_TB_MARK_FLAGGED), Command.ITEM, 10);
        showHeader = new Command(Lang.get(Lang.BTN_MF_HEADERS_DETAILS), Command.ITEM, 11);
        addCommand(exit);
        if(!(this instanceof mujmail.search.SearchBox))
            addCommand(deleteNow);
         if (!(this instanceof Trash) && !(this instanceof mujmail.search.SearchBox)) {
            addCommand(sort);
            
            addCommand(flagged);
        }
        if(this instanceof InBox)
            addCommand(seen);
        addCommand(showHeader);
        setCommandListener(this);
        // TODO (Betlista): this shouldn't be here (my opinion, I think it should be loaded or something)
        this.ordering = Ordering.NATURAL;
        this.criterion = Criterion.TIME;
        vct = new Vector();
        for (int i = 0; i < this.getMessageCount(); i++) {
            MessageHeader mail = storage.getMessageAt(i);
            vct.addElement(mail);
        }
        com.sun.lwuit.List list = bxr.createList(vct, this, viewMessage);
        ListModel lm = list.getModel();
        lm.addDataChangedListener(new com.sun.lwuit.events.DataChangedListener() {

            public void dataChanged(int type, int index) {
                setTitle(getBoxTitle());
                revalidate();
            }
        });
        list.addSelectionListener(new SelectionListener() {

            public void selectionChanged(int arg0, int arg1) {
                setTitle(getBoxTitle());
                revalidate();
            }
        });
        addComponent(BorderLayout.CENTER, list);
    }

    public void actionPerformed(ActionEvent evt) {
         standardButtons(evt.getCommand());
    }

    public void commandAction(com.sun.lwuit.Command c, Displayable d) {
          if (DEBUG) System.out.println( "DEBUG TheBox.commandAction(Command, Displayable)  - displayable: " + d.getClass().toString() );
        standardButtons(c);
    }



    /// Manages standard command actions of the boxes
    private void standardButtons(com.sun.lwuit.Command c) {
        if (c == viewMessage) {
          //  MujMail.mujmail.mailForm.viewMessage(getSelectedHeader(), this);
            new MailForm(mujMail).viewMessage(getSelectedHeader(), this);
        } else if (c == exit) {
            exit();
     } else if (c == deleteNow) {
           TextArea txtArea = new TextArea(Lang.get(Lang.EXTRA_DELETE_CONFIRMATION),2,5,TextArea.UNEDITABLE);
           final Command[] commands ={new Command(Lang.get(Lang.BTN_OK)),new Command(Lang.get(Lang.BTN_CANCEL))};
            if (Dialog.show(Lang.get(Lang.BTN_DELETE)+ "?", txtArea, commands) == commands[0]) {
                MessageHeader mh = getSelectedHeader();
                 if (mh.readStatus == MessageHeader.NOT_READ && this instanceof InBox) {
                       ((InBox)this).changeUnreadMails(-1);
                    }
                if(this instanceof Trash )
                     deleteNowFromBoxAndDB(mh, Trash.TrashModes.NOT_MOVE_TO_TRASH);
                else
                    deleteNowFromBoxAndDB(mh, Trash.TrashModes.MOVE_TO_TRASH);
                if (this instanceof InBox && Settings.delMailFromServer) {
                  //  if (Settings.delMailFromServer) {
                        MailAccount account = (MailAccount) getMujMail().getMailAccounts().get(mh.getAccountID());
                        //get appropriate mailAccount
                        if (account != null && account.isActive()) {
                            account.getProtocol().addDeleted(mh);
                        } //put them to apropriate servers list
                  //  }
                    if (!MujMail.mujmail.getInBox().isSyncRunning() && Settings.delMailFromServer) {
                        for (Enumeration e = getMujMail().getMailAccounts().elements(); e.hasMoreElements();) {
                            account = (MailAccount) e.nextElement();
                            if (account.isActive()) {
                                account.getProtocol().removeMsgs(MujMail.mujmail.getInBox());
                            }
                        }
                    }


                }

            }
        } else if (c == seen) {
                    markSeen(getSelectedHeader());
        } else if (c == flagged) {
                    markFlagged(getSelectedHeader());
        } else if (c == sort) {
            MujMail.mujmail.getSettings().showSortFrm(this);
        } else if (c == showHeader) {
              if (DEBUG) System.out.println( "DEBUG TheBox.standardButtons() - c == showHeader" );
          //  MujMail.mujmail.mailForm.showHeader(getSelectedHeader(), this);
              new MailForm(mujMail).showHeader(getSelectedHeader(), this);
        }

    }

    

    public String toString() {
        return name;
    }
    
    /**
     * Gets the enumeration of all messages in this box.
     * @return the enumeration of all messages in this box
     */
    public Enumeration getMessages() {
        return storage.getEnumeration();
    }
    
    /**
     * @return gets box name
     */
    public final String getName() {
        return name;
    }
    
    /** 
     * Changes name of the box.
     * @param newName Name to set.
     */
    public void setName(String newName) {
        if (newName != null) {
            name = newName;
             setTitle(name);
        }
    }
    
    /**
     * Gets number of messages in this box.
     * If threading is enabled it returns number of messages without empty root
     * messages.
     * 
     * @return the number of messages.
     */
    public int getMessageCount() {
    	final int storageSize = storage.getSize();
    	int emptyRootsNumber = 0;
    	if ( storage instanceof ThreadedEmails ) {
    		emptyRootsNumber = ((ThreadedEmails)storage).getEmptyRootsNumber();
    	}
        return storageSize - emptyRootsNumber;
    }
    public MessageHeader getMessageHeaderAt(int index) {
        return storage.getMessageAt( index );
    }

    public MessageHeader getSelectedHeader() {
//        return storageAt(getSelectedIndex());
        if (DEBUG) System.out.print("Selected index is"+getSelectedIndex());
        return getMessageHeaderAt( getSelectedIndex() );
    }

    /**
     * Indicates whether there proceeds some action beyond the mails in this
     * box.
     * @return true if there proceeds some action beyond the mails int this
     * box
     * 
     * TODO: this is now used only for canceling ticker. Refactor!
     */
    protected boolean isBusy() {
        return false;
    }
    
    /**
     * Displays report message which has originated because of some exception.
     * 
     * @param report message which to display. If it ends with '+' it will
     *  be just printed to the standard output, if it ends with '*', the focus
     *  will be returned back after displaying the message.
     * @param sourceFile the source file where the message which is reported
     *  was originated
     * @param ex the exception because of the message is reported
     */
    public void report(String report, String sourceFile, Exception ex) {
        if (DEBUG) {
            ex.printStackTrace();
        }
        
        report(report, sourceFile);
    }
    
    /**
     * Stores mail to this box. 
     * If this box is persistent, makes the copy of given mail and stores it to
     * the RMS database and container of this box.
     * If this box is not persistent, stores the mail only to the container of
     * this box and does not make a copy.
     * @param header the mail to be stored
     * @return the mail that was stored (in case of persistent box, this is the
     *  copy of mail in parameter)
     */
    public abstract MessageHeader storeMail(MessageHeader header);

    /**
     * Displays report message.
     * 
     * @param report message which to display. If it ends with '+' it will
     *  be just printed to the standard output, if it ends with '*', the focus
     *  will be returned back after displaying the message.
     * @param sourceFile the source file where the message which is reported
     *  was originated
     */
    public synchronized void report(String report, String sourceFile) {
          if (DEBUG) System.out.println("DEBUG " + sourceFile + " - " + report);
        //return;
        if (report.startsWith("+")) //not important messages
        {
            return;
        }
        Displayable  display;
        activity = report;
        if (report.endsWith("*")) //then we need to get back the focus afted displaying an alert
        {
            display = this;
        } else {
            display = this; // Adithya, this was null before.
        }

        if (report.startsWith("100:")) {
            getMujMail().alert.setAlert(this, display, report.substring(4), MyAlert.DEFAULT, AlertType.ERROR);
        } else if (report.startsWith("200:")) {
            getMujMail().alert.setAlert(this, display, report.substring(4), MyAlert.DEFAULT, AlertType.WARNING);
        } else if (report.startsWith("300:")) {
            getMujMail().alert.setAlert(this, display, report.substring(4), MyAlert.DEFAULT, AlertType.INFO);
        }

       revalidate();
        // repaint();

     //   if (report.startsWith("*")) { //important message
      //      Functions.sleep(1500);
       // }
    }

    /**
     * Set the information that will be displayed on the progress bar.
     * Note, that progress bar will be displayed only if isBusy() returns true.
     *
     * TODO: replace by tasks
     * 
     * @param activity the message displayed on the progress bar
     *  if it starts with *, this thread will be 500 milliseconds sleep
     * @param max the number of time units when the activity will be finished
     * @param actual actual number of time units
     */
    private synchronized void setProgress(String activity, int max, int actual) {
        this.activity = activity;
        repaint();

        if (activity.startsWith("*")) {
            Functions.sleep(500);
        }
    }

    public void markAsDeleted(MessageHeader message) {
        if (message != null) {
            if (message.deleted) //was it marked as deleted?
            {
                --deleted;
            } //decrease the counter for marked mails
            else {
                ++deleted;
            }
            message.deleted = !message.deleted; //change its state

            /*
            MailAccount msgAcct = (MailAccount)this.getMujMail().mailAccounts.get(message.accountID);
            
            //Set '\Deleted' flag on server if it's an IMAP account
            if (msgAcct.type == MailAccount.IMAP)
            {
            	msgAcct.protocol.setFlags(message, "(\\Deleted)");
            }
            */

   //anup         shiftSelectedIndex(true);
            bxr.getList().setSelectedIndex(bxr.getList().getSelectedIndex()+1, true);;
            showBox();
           // repaint();
        }
    }

    /**
     * Marks a given message as Seen/Unseen depending on the current flag.
     * In case the message's account is of type IMAP4, command to update
     * IMAP server is send too.
     * @param message		Message to mark as Seen/Unseen
     */
    public void markSeen(MessageHeader message) {
        MailAccount msgAcct = (MailAccount)this.getMujMail().getMailAccounts().get(message.getAccountID());

    	if (message.readStatus == MessageHeader.READ)
    	{
    		message.readStatus = MessageHeader.NOT_READ;
            //Unset '\Seen' flag on server if it's an IMAP account
            if (msgAcct != null && msgAcct.isIMAP())
            {
            	msgAcct.getProtocol().setFlags(message, "(\\Seen)", InProtocol.REMOVE_FLAGS, this);
            }    	
    	}
    	else
    	{
    		message.readStatus = MessageHeader.READ;
            //Set '\Seen' flag on server if it's an IMAP account
            if (msgAcct != null && msgAcct.isIMAP())
            {
            	msgAcct.getProtocol().setFlags(message, "(\\Seen)", InProtocol.SET_FLAGS, this);
            }    	
    	}
    	   if (this instanceof InBox) {
            ((InBox) this).changeUnreadMails(message.readStatus == MessageHeader.READ ? -1 : 1);
        }
    	try {
            // msgAcct.getProtocol().getBox().mailDB.saveHeader(message); // Alf: Why so complicated??
            message.getBox().getMailDB().saveHeader(message);
        } catch (MyException e) {
            MujMail.mujmail.alert.setAlert("Error saving message header", AlertType.ERROR);
        }
        
       // repaint();
    }

    /**
     * Marks a given message as Seen/Unseen depending on the current flag.
     * In case the message's account is of type IMAP4, command to update
     * IMAP server is send too.
     * @param message		Message to mark as Seen/Unseen
     */
    public void markFlagged(MessageHeader message) {
        MailAccount msgAcct = (MailAccount)this.getMujMail().getMailAccounts().get(message.getAccountID());
    	if (message.flagged)
    	{
    		message.flagged = false;
            //Unset '\Flagged' flag on server if it's an IMAP account
            if (msgAcct != null && msgAcct.isIMAP())
            {
            	msgAcct.getProtocol().setFlags(message, "(\\Flagged)", InProtocol.REMOVE_FLAGS, this);
            }
    	}
    	else
    	{
    		message.flagged = true;
            //Set '\Flagged' flag on server if it's an IMAP account
            if (msgAcct != null && msgAcct.isIMAP())
            {
            	msgAcct.getProtocol().setFlags(message, "(\\Flagged)", InProtocol.SET_FLAGS, this);
            }
    	}

    	try {
            //  msgAcct.getProtocol().getBox().mailDB.saveHeader(message);
            message.getBox().getMailDB().saveHeader(message);
    	} catch (MyException e) {
            MujMail.mujmail.alert.setAlert("Error saving message header", AlertType.ERROR);
    	}
    	
    	//repaint();
    }

    /**
     * Do the work of deleting all mails from database. Called by 
     * deleteAllMailsFromBoxAndDB(boolean).
     * If the box is Persistent, should delete all mails from database of this
     * persistent box.
     * If the box is Unpersistent, should delete also messages from containers
     * of boxes to which stored mails belong.
     */
    protected abstract void deleteAllMailsFromDB();
    
    public void exit() {
        getMujMail().mainMenu();
    }
    
    /**
     * Returns true if this box is empty.
     * @return true if this box is empty.
     */
    public boolean isEmpty() {
        return storage.isEmpty();
    }
    
    /**
     * Deletes all mails in this storage from this storage and from database.
     * Note that this storage does not store any mails so the mails will be
     * deleted from databases of other boxes.
     * 
     * @param sure if it is true, deletes mails in spite of the box is busy and
     *  don't ask user.
     */
    public void deleteAllMailsFromBoxAndDB(boolean sure) {
        if (!sure) {
            if (isBusy()) {
                getMujMail().alert.setAlert(this, this, Lang.get(Lang.ALRT_SYS_BUSY), MyAlert.DEFAULT, AlertType.INFO);
            } else {
                getMujMail().alert.setAlert(this, this, Lang.get(Lang.ALRT_SYS_DEL_ALL_CONFIRM), MyAlert.DB_CLEAR_CONFIRM, AlertType.CONFIRMATION);
            }
            return;
        }
        
        deleteAllMailsFromDB();
        
        synchronized (storage) {
            storage.removeAllMessages();
        }
        
        deleted = 0;
        revalidate();
    }
    
    /**
     * Immediately deletes given message.
     * @param message the message to be deleted
     * @param  trashMode describes the storing of deleted mail to trash
     */
    public void deleteNowFromBoxAndDB(final MessageHeader message,
        final Trash.TrashModes trashMode) {
        final Dialog d = new Dialog("");
        d.setLayout(new BorderLayout());
        d.addComponent("Center", new Label(Lang.get(Lang.EXTRA_DELETING)));
        new Thread() {

            public void run() {
                message.deleteFromDBAndBox(TheBox.this, trashMode);
                d.dispose();
            }
        }.start();

        d.show(getHeight() / 2 - 20, getHeight() / 2 - 20, 0, 0, false);
        showBox();
    }
    
    /**
     * Do the physical work of deleting marked mails from box and database.
     * Called by deleteMarkedFromBoxAndDB().
     */
    protected abstract void doDeleteMarkedFromBoxAndDB();

    /**
     * Do batch deleteFromDBAndBox of all messages marked as deleted.
     */
    public void deleteMarkedFromBoxAndDB() {
        if (deleted > 0) {

            doDeleteMarkedFromBoxAndDB();
            showBox();
        }
    }
  
    /** Shows Box on foreground of display */
    void showBox() {
     //Anup   mujMail.getDisplay().setCurrent(this);
        
        show();
    }
    public void onShow()
    {
       reload();
    }
      public void reload(){
         synchronized (storage) {
                Logger.log("Total Msg is"+getMessageCount());
              //  removeAll() ;
               setTitle(getBoxTitle());
                if (storage.isEmpty() || isBusy()) {
                    if (storage.isEmpty()) {
                         vct.removeAllElements();
                    }
                 hideButtons();
             } else {
                 showButtons();
                 vct.removeAllElements();
                for (int i = 0; i < this.getMessageCount(); i++) {
                    MessageHeader mail = storage.getMessageAt(i);
                    vct.addElement(mail);
                }

                
                }
         }
        
      }
  
 
    public int getSelectedIndex() {
        return bxr.getList().getSelectedIndex();
    }

 

    protected void hideButtons() {
        if (!btnsHidden) {
            if(!(this instanceof mujmail.search.SearchBox))
                removeCommand(deleteNow);
            if(!(this instanceof Trash) &&!(this instanceof mujmail.search.SearchBox))
            {
            removeCommand(sort);
            removeCommand(seen);
            removeCommand(flagged);
            }
            removeCommand(showHeader);
            if(!(this instanceof InBox))
                removeCommand(seen);
            btnsHidden = true;

        }
    }

    protected void showButtons() {
        if (btnsHidden) {
            if(!(this instanceof mujmail.search.SearchBox)) 
            addCommand(deleteNow);
            if(!(this instanceof Trash) &&!(this instanceof mujmail.search.SearchBox))
            {
                addCommand(sort);
                addCommand(flagged);
            }
            if(this instanceof InBox)
                 addCommand(seen);
            addCommand(showHeader);
            btnsHidden = false;
        }
    }

    public void resort() {
        if ( storage != null ) {
            synchronized (storage) {
                report(Lang.get(Lang.ALRT_SORTING) + Lang.get(Lang.ALRT_WAIT), SOURCE_FILE);
                storage.sort( ComparatorStrategy.getStrategy().getComparator( ordering, criterion ) );
                //Functions.sort(storage, (byte) (getSortMode() % 2), (byte) (getSortMode() >> 1));
            }
        }
   //Anup     cur = 0;
    }

 
    /**
     * Returns title for the box.
     * @return
     */
    private String getBoxTitle() {
    	final StringBuffer buff = new StringBuffer( getName() );

        // String boxName = size > 0 ? getName() + " (" + (cur + 1) + "/" + (size) + ")" : getName() + " (" + Lang.get(Lang.EMPTY) + ")";
    	final int storageSize = storage.getSize();
    	if ( storageSize > 0 ) {
    		buff.append(" (").append(getSelectedIndex()+1)//cur + 1 - empties ) // number of current message
    			.append('/')
    			.append( getMessageCount() ) // count of all non empty messages
    			.append(')');
    	} else {
    		buff.append(" (").append( Lang.get(Lang.EMPTY) ).append(')');
    	}
    	return buff.toString();
    }
     
}
